/**	
 #	Apex Client wrapper for the YouTube Data API
 *	
 *	See YouTube API documentation
 *	http://code.google.com/apis/youtube/2.0/reference.html
 *	twitter: @plmcgrn
**/
public virtual class YoutubeForce {
	
	//define the XML namespaces for YouTube's API responses
	string app = 'http://www.w3.org/2007/app';
	string media = 'http://search.yahoo.com/mrss/';
	string openSearch = 'http://a9.com/-/spec/opensearch/1.1/';
	string gd = 'http://schemas.google.com/g/2005';
	string yt = 'http://gdata.youtube.com/schemas/2007';	
	string atom = 'http://www.w3.org/2005/Atom';
	
	public string username;
	public string password;
	
	public virtual class YoutubeException extends Exception {}
	public class AuthenticationException extends YoutubeForce.YoutubeException {}

	public YoutubeForce() { }

	/** create a new Youtube client with these credentials
		Calls out to checkCreds to validate credentials.  
	 */
	public YoutubeForce(String un, String pw) {
		//method to check creds
	}
	
	/** create an anonymous GET request **/
	protected virtual HttpRequest createAnonRequest(string method, String path) {
    	HttpRequest req = new HttpRequest();
     	req.setEndpoint(path);
      	req.setMethod(method == null ? 'GET' : method);
      	return req;
	}
	
	/** send the http request to the server **/
    protected virtual HttpResponse execute(HttpRequest req) {
      	System.debug(LoggingLevel.FINE, '\n\nReq: \n' + req + '\n\n');
      	HttpResponse res = new http().send(req);
      	String resBody = res.getBody();
      	System.debug(LoggingLevel.FINE, '\n\nResp: \n' + resBody);
      	if (res.getStatusCode() == 401)
      		throw new AuthenticationException(resBody + '\n\nUsername: ' + this.username + '\nPassword: ' + this.password);
      	else if (res.getStatusCode() != 200)
      		throw new YoutubeException(resBody);      	
      	return res;
    }	
	
	/** get a list of the specified user's playlists **/
	public List<YoutubePlaylist> getUserPlaylists(string uname){
		HttpRequest req = createAnonRequest('GET','http://gdata.youtube.com/feeds/api/users/' + uname + '/playlists?v=2');
		HttpResponse resp = execute(req);
		Dom.Document doc = resp.getBodyDocument();

		//system.debug(LoggingLevel.DEBUG, 'Youtube playlists retrieved: ' + lst);		
		return parsePlaylists(doc);
	}
	
	/** get a list of videos based on the specified playlist id **/
	public List<YoutubeVideo> getVideosFromPlaylist(string playlistId){
		HttpRequest req = createAnonRequest('GET','http://gdata.youtube.com/feeds/api/playlists/' + playlistId + '?v=2');
		HttpResponse resp = execute(req);
		Dom.Document doc = resp.getBodyDocument();
		
		return parseVideosFromList(doc);		
	}
	
	/** get a list of videos based on the specified playlist id **/
	public List<YoutubeVideo> getVideosFromPlaylist(string playlistId, integer maxcount){
		HttpRequest req = createAnonRequest('GET','http://gdata.youtube.com/feeds/api/playlists/' + playlistId + '?v=2&max-count=' + maxcount);
		HttpResponse resp = execute(req);
		Dom.Document doc = resp.getBodyDocument();
		
		return parseVideosFromList(doc);		
	}	
	
	/** get a list of videos that the specified user have favorited
	 *	note: this will throw a YoutubeException if the user's favorites are private **/
	public List<YoutubeVideo> getUserFavoriteVideos(string uname){
		HttpRequest req = createAnonRequest('GET','http://gdata.youtube.com/feeds/api/users/' + uname + '/favorites?v=2');
		HttpResponse resp = execute(req);
		Dom.Document doc = resp.getBodyDocument();
		return parseVideosFromList(doc);		
	}
	
	/** search for videos based on a keyword **/
	public List<YoutubeVideo> searchVideos(string keywords){
		HttpRequest req = createAnonRequest('GET','http://gdata.youtube.com/feeds/api/videos?q=' + encodingutil.urlEncode(keywords,'UTF-8') + '&v=2&max-results=10');
		HttpResponse resp = execute(req);
		Dom.Document doc = resp.getBodyDocument();
		return parseVideosFromList(doc);			
	}
	
	/** retrieve a standard video feed.  accepted feedtypes are:
	 *	top_rated, top_favorites, most_viewed, most_popular, most_recent
	 *	most_discussed, most_responded, recently_featured, watch_on_mobile
	 *
	**/
	public List<YoutubeVideo> getStandardFeed(string feedtype){
		string apiurl = 'http://gdata.youtube.com/feeds/api/standardfeeds/';
		HttpRequest req = createAnonRequest('GET', apiurl + feedtype);
		HttpResponse resp = execute(req);
		Dom.Document doc = resp.getBodyDocument();
		return parseVideosFromList(doc);			
	}	
	
	/** retrieve a regionalstandard video feed.  
	 *
	 *	accepted feedtypes are:	top_rated, top_favorites, most_viewed, most_popular, most_recent
	 *		most_discussed, most_responded, recently_featured, watch_on_mobile
	 *
	 *	accepted regioncodes are: AU, BR, CA, CZ, FR, DE, GB, NL, HK, IN, IE, IL IT, 
	 *		JP, MX, NZ, PL, RU, KR, ES, SE, TW, US
	**/
	public List<YoutubeVideo> getRegionalStandardFeed(string feedtype, string regioncode){
		string apiurl = 'http://gdata.youtube.com/feeds/api/standardfeeds/';
		HttpRequest req = createAnonRequest('GET', apiurl + regioncode + '/' + feedtype + '?v=2');
		HttpResponse resp = execute(req);
		Dom.Document doc = resp.getBodyDocument();
		return parseVideosFromList(doc);			
	}		
	
	/** parse playlist structure to create a List<YoutubePlayList> **/
	private List<YoutubePlaylist> parsePlaylists(Dom.Document doc){
		List<YoutubePlaylist> lst = new List<YoutubePlaylist>();
		Dom.Xmlnode root = doc.getRootElement();
		Dom.Xmlnode[] children = root.getChildren();
		system.debug(LoggingLevel.DEBUG,'\n\nresponse: ' + root + '\n\n');
		dom.xmlnode xml = null;	
		YoutubePlaylist yt = null;	
		for(Dom.Xmlnode x : children){
			if(x.getName() == 'entry'){
				yt = new YoutubePlaylist();
				//system.debug(logginglevel.debug,'child root name: ' + x.getName());
				for(Dom.Xmlnode y : x.getChildren()){
					//system.debug(logginglevel.debug,'child of child name: ' + y.getName());
					if(y.getName() == 'title')
						yt.title = y.getText();
					else if(y.getName() == 'summary')
						yt.summary = y.getText();
					else if(y.getName() == 'published')
						yt.published = y.getText();
					else if(y.getName() == 'updated')
						yt.updated = y.getText();
					else if(y.getName() == 'playlistId')
						yt.playlistId = y.getText();
					else if(y.getName() == 'countHint')
						yt.videocount = integer.valueOf(y.getText());
				}
				lst.add(yt);					
			}
		}
		//system.debug(LoggingLevel.DEBUG, 'Youtube playlists retrieved: ' + lst);		
		return lst;		
	}
	
	/** parse a list of videos in XML to create a List<YoutubeVideo> **/
	private List<YoutubeVideo> parseVideosFromList(Dom.Document doc){
		List<YoutubeVideo> lst = new List<YoutubeVideo>();
		Dom.XMLNode root = doc.getRootElement();
		Dom.XMLNode[] entries = root.getChildren();
		//System.Debug(LoggingLevel.DEBUG, entries);
		
		YoutubeVideo yv = null;		
		for(Dom.XMLNode n : entries){		
			//if the node is 'entry' (video info), get it's information
			if(n.getName() == 'entry'){
				//system.debug(LoggingLevel.DEBUG, n + '\n\n');
				
				//iterate through the nodes for the video's info
				for(dom.xmlnode z : n.getChildren()){
					//find the media:group node
					if(z.getName() == 'group'){
						yv = new YoutubeVideo();
						//get the URL for the video's watch page
						yv.videoId = z.getChildElement('videoid', yt).getText();
						yv.title = z.getChildElement('title',media).getText();
						yv.playerurl = z.getChildElement('player', media).getAttribute('url','');
						yv.thumbnailUrl = z.getChildElement('thumbnail', media).getAttribute('url',''); 
						yv.description = z.getChildElement('description',media).getText();
						yv.duration = integer.valueOf(z.getChildElement('duration', yt).getAttribute('seconds',''));
						//system.debug(LoggingLevel.DEBUG, 'URL: ' + playerurl);
						
						//add a new video to the list based on the metadata from this element
						lst.add(yv);				
					}
				}
			}
		}			
	return lst;	
	}	
}